package frontend.pages.worklog.morning;

import backend.model.admin.*;
import backend.model.worklog.WorkLog;
import backend.service.admin.ProjectService;
import backend.service.admin.SfiService;
import backend.service.admin.ShiftService;
import backend.service.admin.WorkplaceService;
import backend.service.charisma.CharismaDataSynchronizer;
import backend.service.worklog.WorkLogService;
import com.google.common.collect.Lists;
import frontend.components.form.ValidatableForm;
import frontend.components.other.TeamSelectionPanel;
import frontend.core.TimeTrackingWebApplication;
import frontend.model.worklog.MorningWorkLogUIModel;
import shared.util.CalendarUtils;
import frontend.components.base.ComponentFactory;
import frontend.components.other.DBModelChoiceRenderer;
import frontend.components.other.UserInputCallableAction;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import org.apache.wicket.Localizer;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.util.CollectionModel;
import org.apache.wicket.validation.ValidationError;
import shared.exceptions.CharismaConnectionDownException;

import java.util.*;

public class MorningWorkLogForm extends ValidatableForm<MorningWorkLogUIModel> {
    private static Logger logger = Logger.getLogger(MorningWorkLogForm.class);

    private static final String RESOURCE_KEY_FIELD_LABEL_EMPLOYEES = "pages.morningWorkLogPage.form.employees";
    private static final String RESOURCE_KEY_FIELD_LABEL_TEAM = "pages.morningWorkLogPage.form.team";
    private static final String RESOURCE_KEY_FIELD_LABEL_DATE = "pages.morningWorkLogPage.form.date";
    public static final String RESOURCE_KEY_FIELD_LABEL_PROJECT = "pages.morningWorkLogPage.form.project";
    public static final String RESOURCE_KEY_FIELD_LABEL_WORKPLACE = "pages.morningWorkLogPage.form.workplace";
    public static final String RESOURCE_KEY_FIELD_LABEL_WORK_DETAIL = "pages.morningWorkLogPage.form.workDetail";
    public static final String RESOURCE_KEY_FIELD_LABEL_SFI = "pages.morningWorkLogPage.form.sfi";
    public static final String RESOURCE_KEY_FIELD_LABEL_SHIFT = "pages.morningWorkLogPage.form.shift";
    public static final String RESOURCE_KEY_ERROR_CHARISMA_DOWN = "pages.morningWorkLogPage.form.error.charismaDown";
    public static final String RESOURCE_KEY_INFO_SAVE_SUCCESSFUL = "pages.morningWorkLogPage.form.info.saveSuccessful";

    private Panel dateComponent;
    private Panel employeesComponent;
    private CollectionModel<Employee> absentEmployeesListModel;

    private Panel workDetailComponent;
    private CollectionModel workDetailsCollectionModel;
    private Panel sfiComponent;
    private Panel workplaceComponent;
    private Panel shiftComponent;

    public MorningWorkLogForm(String id, MorningWorkLogUIModel uiModel) {
        super(id, uiModel);
    }

    private WorkLogService getWorkLogService() {
        return TimeTrackingWebApplication.getService(WorkLogService.class);
    }

    private CharismaDataSynchronizer getCharismaDataSynchronizer() {
        return TimeTrackingWebApplication.getBean(CharismaDataSynchronizer.class);
    }

    protected void constructFormContent() {
        uiModel.setDate(new Date());
        dateComponent = ComponentFactory.createDatePickerComponent("date", RESOURCE_KEY_FIELD_LABEL_DATE, uiModel, getOnDateSelectAction());
        Panel teamComponent = new TeamSelectionPanel("team", uiModel, getOnTeamSelectAction() , RESOURCE_KEY_FIELD_LABEL_TEAM, true);
        absentEmployeesListModel = new CollectionModel<Employee>(new HashSet<Employee>());
        employeesComponent = ComponentFactory.createPaletteComponent("presentEmployees", uiModel, absentEmployeesListModel, 10, false);
        Panel projectComponent = ComponentFactory.createDropDownChoiceComponent(
                "project", RESOURCE_KEY_FIELD_LABEL_PROJECT, uiModel, getAllActiveProjects(), new DBModelChoiceRenderer<Project>(), false, getOnProjectSelectAction());
        sfiComponent = ComponentFactory.createDropDownChoiceComponent(
                "sfi", RESOURCE_KEY_FIELD_LABEL_SFI, uiModel, getAllActiveSfis(), new DBModelChoiceRenderer<Sfi>(), false);
        workplaceComponent = ComponentFactory.createDropDownChoiceComponent("workplace", RESOURCE_KEY_FIELD_LABEL_WORKPLACE, uiModel,
                new CollectionModel(getAllActiveWorkplaces()), new DBModelChoiceRenderer<Workplace>(), false, getOnWorkplaceSelectAction());
        shiftComponent = ComponentFactory.createDropDownChoiceComponent(
                "shift", RESOURCE_KEY_FIELD_LABEL_SHIFT, uiModel, getAllActiveShifts(), new DBModelChoiceRenderer<Shift>(), false);
        workDetailsCollectionModel = new CollectionModel(getWorkDetailsForWorkplace());
        workDetailComponent = ComponentFactory.createDropDownChoiceComponent(
                "workDetail", RESOURCE_KEY_FIELD_LABEL_WORK_DETAIL, uiModel, workDetailsCollectionModel, new DBModelChoiceRenderer<WorkDetail>(), true, null);
        AjaxButton isPresentButton = createIsPresentButton();
        add(dateComponent, teamComponent, employeesComponent, projectComponent, workplaceComponent, sfiComponent, shiftComponent, workDetailComponent, isPresentButton);
    }

    private List<Project> getAllActiveProjects() {
        List<Project> projects = new ArrayList<Project>();
        projects.addAll(TimeTrackingWebApplication.getService(ProjectService.class).findAllActiveProjects());
        Collections.sort(projects);
        return projects;
    }

    private List<Workplace> getAllActiveWorkplaces() {
        List<Workplace> workplaces = new ArrayList<Workplace>();
        workplaces.addAll(TimeTrackingWebApplication.getService(WorkplaceService.class).findAllActiveWorkplaces());
        Collections.sort(workplaces);
        return workplaces;
    }

    private List<Sfi> getAllActiveSfis() {
        List<Sfi> sfis = new ArrayList<Sfi>();
        sfis.addAll(TimeTrackingWebApplication.getService(SfiService.class).findAllActiveSfis());
        Collections.sort(sfis);
        return sfis;
    }

    private List<Shift> getAllActiveShifts() {
        List<Shift> shifts = new ArrayList<Shift>();
        shifts.addAll(TimeTrackingWebApplication.getService(ShiftService.class).findAllActiveShifts());
        Collections.sort(shifts);
        return shifts;
    }

    private List<WorkDetail> getWorkDetailsForWorkplace() {
        List<WorkDetail> workDetails = new ArrayList<WorkDetail>();
        if (uiModel.getWorkplace() != null) {
            workDetails = Lists.newArrayList(uiModel.getWorkplace().getWorkDetails());
            Collections.sort(workDetails);
        }
        return workDetails;
    }

    private UserInputCallableAction<Project> getOnProjectSelectAction() {
        return new UserInputCallableAction<Project>() {
            @Override
            public void call(AjaxRequestTarget target, Project project) {
                if (project != null) {
                    boolean isDirectPayment = project.getIsDirectPayment();
                    updateFormComponentsEnabledProperty(isDirectPayment);
                    target.add(sfiComponent, shiftComponent, workplaceComponent, workDetailComponent);
                }
            }
        };
    }

    private void updateFormComponentsEnabledProperty(boolean directPayment) {
        sfiComponent.setEnabled(directPayment);
        shiftComponent.setEnabled(directPayment);
        workplaceComponent.setEnabled(directPayment);
        workDetailComponent.setEnabled(directPayment);
    }

    private AjaxButton createIsPresentButton() {
        AjaxButton showWorkLogButton = new AjaxButton("isPresent", this) {
            @Override
            protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
                validationErrors.clear();
                doFormValidation();
                if (validationErrors.size() > 0) {
                    for (ValidationError validationError : validationErrors) {
                        error(validationError.getMessage());
                    }
                } else {
                    try {
                        doMorningAttendance();
                        info(CalendarUtils.appendCurrentTime(Localizer.get().getString(RESOURCE_KEY_INFO_SAVE_SUCCESSFUL, this)));
                    } catch (CharismaConnectionDownException e) {
                        logger.error("Charisma database connection down. Please contact the Charisma system administrator.", e);
                        info(CalendarUtils.appendCurrentTime(Localizer.get().getString(RESOURCE_KEY_INFO_SAVE_SUCCESSFUL, this)));
                        error(CalendarUtils.appendCurrentTime(Localizer.get().getString(RESOURCE_KEY_ERROR_CHARISMA_DOWN, this)));
                    }
                }
                target.add(feedbackPanel);
            }

            @Override
            protected void onError(AjaxRequestTarget target, Form<?> form) {
                logger.error("Error occurred while submitting form.");
            }
        };
        return showWorkLogButton;
    }

    public UserInputCallableAction<Team> getOnTeamSelectAction() {
        return new UserInputCallableAction<Team>() {
            @Override
            public void call(AjaxRequestTarget target, Team team) {
                if (team != null && uiModel.getDate() != null) {
                    setPresentAndAbsentEmployees(team, uiModel.getDate());
                } else {
                    uiModel.setPresentEmployees(new HashSet<Employee>());
                    absentEmployeesListModel.setObject(new HashSet<Employee>());
                }
                target.add(feedbackPanel);
                target.add(employeesComponent);
            }
        };
    }

    public UserInputCallableAction<Date> getOnDateSelectAction() {
        return new UserInputCallableAction<Date>() {

            @Override
            public void call(AjaxRequestTarget target, Date date) {
                if (date != null && uiModel.getTeam() != null) {
                    setPresentAndAbsentEmployees(uiModel.getTeam(), date);
                } else {
                    uiModel.setPresentEmployees(new HashSet<Employee>());
                    absentEmployeesListModel.setObject(new HashSet<Employee>());
                }
                target.add(feedbackPanel);
                target.add(employeesComponent);
            }
        };
    }

    public UserInputCallableAction<Workplace> getOnWorkplaceSelectAction() {
        return new UserInputCallableAction<Workplace>() {
            @Override
            public void call(AjaxRequestTarget target, Workplace workplace) {
                List<WorkDetail> workDetails = new ArrayList<WorkDetail>();
                if (workplace != null && workplace.getWorkDetails() != null) {
                    workDetails = Lists.newArrayList(workplace.getWorkDetails());
                    Collections.sort(workDetails);
                }
                workDetailsCollectionModel.setObject(workDetails);
                target.add(feedbackPanel);
                target.add(workDetailComponent);
            }
        };
    }

    private void setPresentAndAbsentEmployees(Team team, Date date) {
        Set<Employee> presentEmployees = getPresentEmployees(team, date);
        uiModel.setPresentEmployees(presentEmployees);
        absentEmployeesListModel.setObject(getAbsentEmployees(team, presentEmployees));
    }

    private Set<Employee> getPresentEmployees(Team team, Date date) {
        Set<Employee> presentEmployees = new HashSet<Employee>();
        WorkLogService workLogService = TimeTrackingWebApplication.getService(WorkLogService.class);
        List<WorkLog> todayWorkLogs = workLogService.loadWorkLogForTimeIntervalAndTeam(team, date, date);
        for (WorkLog workLog : todayWorkLogs) {
            presentEmployees.add(workLog.getEmployee());
        }
        return presentEmployees;
    }

    private TreeSet<Employee> getAbsentEmployees(Team team, Set<Employee> presentEmployees) {
        Collection<Employee> absentEmployees = CollectionUtils.subtract(team.getEmployees(), presentEmployees);
        return new TreeSet<>(absentEmployees);
    }

    private void doMorningAttendance() throws CharismaConnectionDownException {
        WorkLogService workLogService = getWorkLogService();
        CharismaDataSynchronizer charismaDataSynchronizer = getCharismaDataSynchronizer();

        Set<Employee> presentEmployees = uiModel.getPresentEmployees();
        Collection<WorkLog> workLogsToBeAdded = new HashSet<WorkLog>();
        Date date = uiModel.getDate();
        for (Employee employee : presentEmployees) {
            List<WorkLog> todayWorkLogs = workLogService.loadWorkLogForDateAndEmployee(employee, date);
            // add presence only if no other WorkLog entries are found for this employee
            if (todayWorkLogs.size() == 0) {
                WorkLog workLog = createWorkLog(employee, date);
                workLogsToBeAdded.add(workLog);
            }
        }

        Collection<Employee> absentEmployees = getAbsentEmployees(uiModel.getTeam(), presentEmployees);
        Collection<WorkLog> workLogsToBeDeleted = new HashSet<WorkLog>();
        for (Employee employee : absentEmployees) {
            List<WorkLog> todayWorkLogs = workLogService.loadWorkLogForDateAndEmployee(employee, date);
            workLogsToBeDeleted.addAll(todayWorkLogs);
        }

        workLogService.bulkSaveDBModels(workLogsToBeAdded);
        workLogService.bulkDeleteDBModels(workLogsToBeDeleted);
        charismaDataSynchronizer.validateHoursForEmployeeListAndTimeInterval(Lists.newArrayList(uiModel.getTeam().getEmployees()), date, date);
    }

    private WorkLog createWorkLog(Employee employee, Date date) {
        WorkLog workLog = new WorkLog();
        workLog.setEmployee(employee);
        workLog.setDate(date);
        workLog.setOccupation(employee.getOccupation());
        workLog.setForeman(employee.getTeam().getForeman());
        workLog.setTeam(employee.getTeam());
        workLog.setProject(uiModel.getProject());
        int hours = 0;
        if (uiModel.getProject().getIsDirectPayment()) {
            workLog.setWorkplace(uiModel.getWorkplace());
            workLog.setSfi(uiModel.getSfi());
            workLog.setShift(uiModel.getShift());
            workLog.setWorkDetail(uiModel.getWorkDetail());
            hours = 8;
        }
        workLog.setHours(hours);
        return workLog;
    }

    @Override
    protected void doFormValidation() {
        validateNotEmpty(uiModel.getTeam(), RESOURCE_KEY_FIELD_LABEL_TEAM);
        validateNotEmpty(uiModel.getDate(), RESOURCE_KEY_FIELD_LABEL_DATE);
        if (uiModel.getProject() != null || uiModel.getWorkplace() != null || uiModel.getShift() != null || uiModel.getSfi() != null) {
            validateNotEmptySelection(uiModel.getPresentEmployees(), RESOURCE_KEY_FIELD_LABEL_EMPLOYEES);
        }
        if (false == uiModel.getPresentEmployees().isEmpty()) {
            validateNotEmpty(uiModel.getProject(), RESOURCE_KEY_FIELD_LABEL_PROJECT);
            if (uiModel.getProject() != null && uiModel.getProject().getIsDirectPayment()) {
                validateNotEmpty(uiModel.getWorkplace(), RESOURCE_KEY_FIELD_LABEL_WORKPLACE);
                validateNotEmpty(uiModel.getSfi(), RESOURCE_KEY_FIELD_LABEL_SFI);
                validateNotEmpty(uiModel.getShift(), RESOURCE_KEY_FIELD_LABEL_SHIFT);
            }
        }
    }
}